In this lesson, we will learn to stack dataframes on top of each other and side by side.
The dataframes we will stack on top of each other will be the governor’s two 2019 half-yearly data frames. We will do that on Wednesday.
The dataframes we will join side-by-side will be the voters_yancey and vhis_yancey. We will do this today.
We start with two dataframes: * voters_yancey * vhis_yancey
Can you remember how to tell how many columns and rows each has?
- voters_yancey = 16,351 x 71
- vhis_yancey = 79,915 x 15
Before we get started, let’s load the tidyverse packages into our library…
rr library(tidyverse)
(We will be using the “tidy” way of joining tables together, but JOIN functions from tidyverse’s dplyr package are very similar to the merge() function in base R.)
You may also need to load the data:
rr # This is not yet a publicly accessible URL.
#yancey_url<- ://adminliveunc.sharepoint.com/sites/MEJO570Spring2020/Shared%20Documents/Joins%20Lectures/March2325.RData
#download.file(yancey_url, destfile = ./March2325.RData)
#load(./March2325.RData)
load(~/Downloads/March2325.RData)
The first thing we will do is create a dataframe called all_yancey. We will create it by applying the left_join function to the voters_yancey and vhis_yancey dataframes.
rr all_yancey<- left_join(voters_yancey, vhis_yancey)
Joining, by = c(\county_id\, \county_desc\, \voter_reg_num\, \ncid\)
rr all_yancey
You can see that the new dataframe we created has 81,202 rows x 82 columns. (It has all 16,351 voters rows x every time it matches a row in vhis_yancey.)
You might ask yourself, “How does R know that a row in one dataframe is supposed to be joined with a row in another dataframe?”
The join functions will look at the column names that are common to both and make the assumption that these are the columns on which they should be joined. This is a feature that you do not find in the SQL database language that is commonly used to query databases.
It’s a nice feature as long as it works. Here are reasons it might not work:
The columns in the two dataframes have matching names, but they contain different types of information. For example the word “name” is a pretty generic column. But in one dataframe it might be the name of a person and in the other it might be the name of a street. You wouldn’t want to join those.
The two dataframes might have columns in common, but they might be called different things. For example, the names of streets might be called “StName” in one dataframe and “Street_Name” in another. These would not match, but should.
The choice of columns that R makes might not yield uniqueness. In our example here, we just need to join on “ncid” because – allegedly – that number is unique to each voter across the state. But if we were joining a dataframe that had all 100 counties in it and we didn’t have ncid, we would need to join by both county_id as well as voter_reg_num because voter_reg_num may be duplicated from county to county. (Incidentally, in this case we do not need county_desc. It’s a synonym for county_id and just slows down the matching.)
Order of arguments matters
Let’s switch the order in which we pass dataframes to the left_join function.
rr left_join(vhis_yancey, voters_yancey)
Joining, by = c(\county_id\, \county_desc\, \voter_reg_num\, \ncid\)
This time we get about 2,000 fewer rows. We get 79,915 and 82 columns. This is all 79,915 rows from vhis x every time it matches a row in the voters dataframe.
Explicitly stating the columns to join
We can also explicitly tell R which columns to use to join. We do this by adding the “by” argument and setting its value to the name of whatever column we want to use in the join. In this case, by = “ncid”.
rr left_join(vhis_yancey, voters_yancey, by = )
This also results in 79,915 rows, but this time we get 85 columns. So we end up with some duplicate columns because we didn’t use them in the join. Note how they are designated with a “.y” or “.x” that gets appended to the names.
(Note: This data isn’t always so clean. I’ve run this exact same query with older versions of these exact same tables and found that it appeared that voters with the same ncid voted twice in the same election. Always be on the lookout for things like when you’re joining dataframes. Remember: When you see things like that it means only one of two things – a good story or bad data. In either case, interviews with humans are required.)
When can drop those three duplicate columns if we want …
rr all_yancey <- left_join(voters_yancey, vhis_yancey, by = )
all_yancey %>% select(-county_id.y, -county_desc.y, -voter_reg_num.y)
Is there a right_join?
Yes, there is such a thing as a right join, but I just use left_join and change the order of the dataframe arguments.
Full Joins
Full joins return all rows and all columns from both dataframes.
rr full_yancey <- full_join(voters_yancey, vhis_yancey, by=)
full_yancey
This gives us 81,294 rows. This is one row for each row in the voters_yancey dataframe that matches a row in the vhis_yancey dataframe PLUS all of the rows in either dataframe that do not have a match.
As you can see in the next line of code, the order of arguments in full joins does not matter.
rr full_join(vhis_yancey, voters_yancey, by=) r NA
Inner Joins
With inner joins, rows that don’t have a match are just dropped.
rr inner_yancey <- inner_join(vhis_yancey, voters_yancey, by=)
inner_yancey
Like in full_joins the order of arguments does not matter.
rr inner_join(voters_yancey, vhis_yancey, by=)
Practical Application
That’s all cool in theory, but in what practical cases would you want to use one kind of join rather than another.
One example is to find voters who are registered but have never voted. For this we will use a left join. We will first do a left join and then filter to find all rows that have values in the election_lbl column.
Remember, this is saying: “Hey R, give me all the voters from the voters_yancey dataframe. Match those to their voting history anytime there is a row in the vhis_yancey dataframe that has a matching ncid.”
Because you are asking for all voters regardless of whether they have a match in the vhis dataframe, you will get values in any of the ncvhis columns anytime there isn’t a match.
This means that after doing the join, we can find the rows that have values in the voter history dataframe and know that these are voters for whom there is no record of them participatig in an election.
rr all_yancey<- left_join(voters_yancey, vhis_yancey, by = )
all_yancey %>% filter(is.na(election_lbl))
It appears there are 1,379 people in Yancey County who are registered but you have never voted. (Note: Not all of those voters are “Active,” but that’s a conversation for a different day.)
If we switch the order of arguments in the left join, we would get the names of anyone anyone who voted in a Yancey County election who is not currently registered in Yancey County.
rr all_yancey<- left_join(vhis_yancey, voters_yancey, by = )
all_yancey %>% filter(is.na(last_name))
You will see that it appears as if there are 92 voter history records that do not match an ncid of a person who is on the voter registration roles in Yancey County. It would be important to understand that why this is the case. This may be a bookkeeping anamoly or something else.
We might wonder whether these 92 voter history records that do not appear to have a matching person are 92 instances of a particular person, 85 different people in one election, or some combination.
Let’s ask R to show us the rows that are missing a last name… then group those rows by ncid (one group for each unique ncid) … then count the number of rows in each group… then put the result of that counting summary into a column called “unlisted” (although we could call that column anything) … and then show us the results of all that ordered by the counting summary values in the unlisted column in descending order.
rr all_yancey %>% filter(is.na(last_name)) %>% group_by(ncid) %>% summarize(unlisted = n()) %>% arrange(desc(unlisted))
OK, so it’s 38 unique ncid values … 38 “people”. And look at the values in the ncid column. They are different lengths. That seems strange because unique identifiers – like Social Security numbers – are usually the same number of characters.
Sure enough, if we look at https://s3.amazonaws.com/dl.ncsbe.gov/data/layout_ncvoter_ncvhis.txt it says that the ncid column should be 12 characters long. (In reality, most I see are six characters long. But there’s clearly a discrepency here that might help us inquire with the agency about it.)
The next thing we might do is take a look at just one of these ncids for which there is a missing person. Maybe we will see a pattern…
rr all_yancey %>% filter(ncid==39580) %>% arrange(election_lbl)
Interesting. All of these show that the “voted_county_desc” is CABARRUS. That makes me wonder if this weirdness is an artifact of people moving between counties.
I wonder if the voter history records that have missing people all come from certain counties or from certain election dates. Maybe some of the records just haven’t transferred yet.
Let’s make one group for each county, and then sub-groups for each election date (in this cased called “election_lbl”) for each county.
rr all_yancey %>% filter(is.na(last_name)) %>% group_by(voted_county_desc, election_lbl) %>% summarize(unlisted = n()) %>% arrange(desc(unlisted))
Filtering Joins
Before we finish, let’s look at two other types of joins that also have specific practical application to our problem here.
We will now look at semi_join and anti_join. These are called “filtering joins” because they use the second dataframe in the function to filter rows from the first dataframe.
If you want to see only voters that have voted, use “semi_join”. A semi_join is like an inner_join, but instead of giving you one row from the first dataframe EVERY time it matches a row in the second dataframe, this gives you only unique values from the first dataframe. And it only gives you columns from the from the first dataframe. It is just filtering the first dataframe, not really joining it with the second.
rr semi_join(voters_yancey, vhis_yancey, by=)
Note that the number of columns in this result are equal to the number of columns in voters_yancey.
If you want to see only voters who have NEVER voted, we use anti_join. This gives us only rows from the first dataframe that have NO matches in the second dataframe.
rr anti_join(voters_yancey, vhis_yancey, by=)
So now we’ve covered the main ways you would join dataframes side by side. In the next lesson we will look at different ways to put dataframes on top of each other.
LS0tCnRpdGxlOiAiQ2xhc3MgMTc6IEpvaW5pbmcgRGF0YSBGcmFtZXMiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCi0tLQp0aXRsZTogIkNsYXNzIDE3OiBKb2luaW5nIERhdGEgRnJhbWVzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpJbiB0aGlzIGxlc3Nvbiwgd2Ugd2lsbCBsZWFybiB0byBzdGFjayBkYXRhZnJhbWVzIG9uIHRvcCBvZiBlYWNoIG90aGVyIGFuZCBzaWRlIGJ5IHNpZGUuCgpUaGUgZGF0YWZyYW1lcyB3ZSB3aWxsIHN0YWNrIG9uIHRvcCBvZiBlYWNoIG90aGVyIHdpbGwgYmUgdGhlIGdvdmVybm9yJ3MgdHdvIDIwMTkgaGFsZi15ZWFybHkgZGF0YSBmcmFtZXMuIFdlIHdpbGwgZG8gdGhhdCBvbiBXZWRuZXNkYXkuCgpUaGUgZGF0YWZyYW1lcyB3ZSB3aWxsIGpvaW4gc2lkZS1ieS1zaWRlIHdpbGwgYmUgdGhlIHZvdGVyc195YW5jZXkgYW5kIHZoaXNfeWFuY2V5LiBXZSB3aWxsIGRvIHRoaXMgdG9kYXkuCgpXZSBzdGFydCB3aXRoIHR3byBkYXRhZnJhbWVzOgoqIHZvdGVyc195YW5jZXkgCiogdmhpc195YW5jZXkgCgpDYW4geW91IHJlbWVtYmVyIGhvdyB0byB0ZWxsIGhvdyBtYW55IGNvbHVtbnMgYW5kIHJvd3MgZWFjaCBoYXM/CgoqIHZvdGVyc195YW5jZXkgPSAxNiwzNTEgeCA3MQoqIHZoaXNfeWFuY2V5ID0gNzksOTE1IHggMTUKCgpCZWZvcmUgd2UgZ2V0IHN0YXJ0ZWQsIGxldCdzIGxvYWQgdGhlIHRpZHl2ZXJzZSBwYWNrYWdlcyBpbnRvIG91ciBsaWJyYXJ5Li4uCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKKFdlIHdpbGwgYmUgdXNpbmcgdGhlICJ0aWR5IiB3YXkgb2Ygam9pbmluZyB0YWJsZXMgdG9nZXRoZXIsIGJ1dCBKT0lOIGZ1bmN0aW9ucyBmcm9tIHRpZHl2ZXJzZSdzIGRwbHlyIHBhY2thZ2UgYXJlIHZlcnkgc2ltaWxhciB0byB0aGUgbWVyZ2UoKSBmdW5jdGlvbiBpbiBiYXNlIFIuKQoKCgpZb3UgbWF5IGFsc28gbmVlZCB0byBsb2FkIHRoZSBkYXRhOgpgYGB7cn0KIyBUaGlzIGlzIG5vdCB5ZXQgYSBwdWJsaWNseSBhY2Nlc3NpYmxlIFVSTC4gCgojeWFuY2V5X3VybDwtICJodHRwczovL2FkbWlubGl2ZXVuYy5zaGFyZXBvaW50LmNvbS9zaXRlcy9NRUpPNTcwU3ByaW5nMjAyMC9TaGFyZWQlMjBEb2N1bWVudHMvSm9pbnMlMjBMZWN0dXJlcy9NYXJjaDIzMjUuUkRhdGEiCgojZG93bmxvYWQuZmlsZSh5YW5jZXlfdXJsLCBkZXN0ZmlsZSA9ICIuL01hcmNoMjMyNS5SRGF0YSIpCgojbG9hZCgiLi9NYXJjaDIzMjUuUkRhdGEiKQoKbG9hZCgifi9Eb3dubG9hZHMvTWFyY2gyMzI1LlJEYXRhIikKCgpgYGAKCgoKVGhlIGZpcnN0IHRoaW5nIHdlIHdpbGwgZG8gaXMgY3JlYXRlIGEgZGF0YWZyYW1lIGNhbGxlZCBhbGxfeWFuY2V5LiBXZSB3aWxsIGNyZWF0ZSBpdCBieSBhcHBseWluZyB0aGUgbGVmdF9qb2luIGZ1bmN0aW9uIHRvIHRoZSB2b3RlcnNfeWFuY2V5IGFuZCB2aGlzX3lhbmNleSBkYXRhZnJhbWVzLgoKCmBgYHtyfQphbGxfeWFuY2V5PC0gbGVmdF9qb2luKHZvdGVyc195YW5jZXksIHZoaXNfeWFuY2V5KQoKYWxsX3lhbmNleQpgYGAKCllvdSBjYW4gc2VlIHRoYXQgdGhlIG5ldyBkYXRhZnJhbWUgd2UgY3JlYXRlZCBoYXMgODEsMjAyIHJvd3MgeCA4MiBjb2x1bW5zLiAoSXQgaGFzIGFsbCAxNiwzNTEgdm90ZXJzIHJvd3MgeCBldmVyeSB0aW1lIGl0IG1hdGNoZXMgYSByb3cgaW4gdmhpc195YW5jZXkuKQoKWW91IG1pZ2h0IGFzayB5b3Vyc2VsZiwgIkhvdyBkb2VzIFIga25vdyB0aGF0IGEgcm93IGluIG9uZSBkYXRhZnJhbWUgaXMgc3VwcG9zZWQgdG8gYmUgam9pbmVkIHdpdGggYSByb3cgaW4gYW5vdGhlciBkYXRhZnJhbWU/IgoKVGhlIGpvaW4gZnVuY3Rpb25zIHdpbGwgbG9vayBhdCB0aGUgY29sdW1uIG5hbWVzIHRoYXQgYXJlIGNvbW1vbiB0byBib3RoIGFuZCBtYWtlIHRoZSBhc3N1bXB0aW9uIHRoYXQgdGhlc2UgYXJlIHRoZSBjb2x1bW5zIG9uIHdoaWNoIHRoZXkgc2hvdWxkIGJlIGpvaW5lZC4gVGhpcyBpcyBhIGZlYXR1cmUgdGhhdCB5b3UgZG8gbm90IGZpbmQgaW4gdGhlIFNRTCBkYXRhYmFzZSBsYW5ndWFnZSB0aGF0IGlzIGNvbW1vbmx5IHVzZWQgdG8gcXVlcnkgZGF0YWJhc2VzLgoKSXQncyBhIG5pY2UgZmVhdHVyZSBhcyBsb25nIGFzIGl0IHdvcmtzLiBIZXJlIGFyZSByZWFzb25zIGl0IG1pZ2h0IG5vdCB3b3JrOgoKKiBUaGUgY29sdW1ucyBpbiB0aGUgdHdvIGRhdGFmcmFtZXMgaGF2ZSBtYXRjaGluZyBuYW1lcywgYnV0IHRoZXkgY29udGFpbiBkaWZmZXJlbnQgdHlwZXMgb2YgaW5mb3JtYXRpb24uIEZvciBleGFtcGxlIHRoZSB3b3JkICJuYW1lIiBpcyBhIHByZXR0eSBnZW5lcmljIGNvbHVtbi4gQnV0IGluIG9uZSBkYXRhZnJhbWUgaXQgbWlnaHQgYmUgdGhlIG5hbWUgb2YgYSBwZXJzb24gYW5kIGluIHRoZSBvdGhlciBpdCBtaWdodCBiZSB0aGUgbmFtZSBvZiBhIHN0cmVldC4gWW91IHdvdWxkbid0IHdhbnQgdG8gam9pbiB0aG9zZS4KCiogVGhlIHR3byBkYXRhZnJhbWVzIG1pZ2h0IGhhdmUgY29sdW1ucyBpbiBjb21tb24sIGJ1dCB0aGV5IG1pZ2h0IGJlIGNhbGxlZCBkaWZmZXJlbnQgdGhpbmdzLiBGb3IgZXhhbXBsZSwgdGhlIG5hbWVzIG9mIHN0cmVldHMgbWlnaHQgYmUgY2FsbGVkICJTdE5hbWUiIGluIG9uZSBkYXRhZnJhbWUgYW5kICJTdHJlZXRfTmFtZSIgaW4gYW5vdGhlci4gVGhlc2Ugd291bGQgbm90IG1hdGNoLCBidXQgc2hvdWxkLgoKKiBUaGUgY2hvaWNlIG9mIGNvbHVtbnMgdGhhdCBSIG1ha2VzIG1pZ2h0IG5vdCB5aWVsZCB1bmlxdWVuZXNzLiBJbiBvdXIgZXhhbXBsZSBoZXJlLCB3ZSBqdXN0IG5lZWQgdG8gam9pbiBvbiAibmNpZCIgYmVjYXVzZSAtLSBhbGxlZ2VkbHkgLS0gdGhhdCBudW1iZXIgaXMgdW5pcXVlIHRvIGVhY2ggdm90ZXIgYWNyb3NzIHRoZSBzdGF0ZS4gQnV0IGlmIHdlIHdlcmUgam9pbmluZyBhIGRhdGFmcmFtZSB0aGF0IGhhZCBhbGwgMTAwIGNvdW50aWVzIGluIGl0IGFuZCB3ZSBkaWRuJ3QgaGF2ZSBuY2lkLCB3ZSB3b3VsZCBuZWVkIHRvIGpvaW4gYnkgYm90aCBjb3VudHlfaWQgYXMgd2VsbCBhcyB2b3Rlcl9yZWdfbnVtIGJlY2F1c2Ugdm90ZXJfcmVnX251bSBtYXkgYmUgZHVwbGljYXRlZCBmcm9tIGNvdW50eSB0byBjb3VudHkuIChJbmNpZGVudGFsbHksIGluIHRoaXMgY2FzZSB3ZSBkbyBub3QgbmVlZCBjb3VudHlfZGVzYy4gSXQncyBhIHN5bm9ueW0gZm9yIGNvdW50eV9pZCBhbmQganVzdCBzbG93cyBkb3duIHRoZSBtYXRjaGluZy4pCgoKKipPcmRlciBvZiBhcmd1bWVudHMgbWF0dGVycyoqCgpMZXQncyBzd2l0Y2ggdGhlIG9yZGVyIGluIHdoaWNoIHdlIHBhc3MgZGF0YWZyYW1lcyB0byB0aGUgbGVmdF9qb2luIGZ1bmN0aW9uLgoKYGBge3J9CmxlZnRfam9pbih2aGlzX3lhbmNleSwgdm90ZXJzX3lhbmNleSkKYGBgCgpUaGlzIHRpbWUgd2UgZ2V0IGFib3V0IDIsMDAwIGZld2VyIHJvd3MuIFdlIGdldCA3OSw5MTUgYW5kIDgyIGNvbHVtbnMuIFRoaXMgaXMgYWxsIDc5LDkxNSByb3dzIGZyb20gdmhpcyB4IGV2ZXJ5IHRpbWUgaXQgbWF0Y2hlcyBhIHJvdyBpbiB0aGUgdm90ZXJzIGRhdGFmcmFtZS4KCgoqKkV4cGxpY2l0bHkgc3RhdGluZyB0aGUgY29sdW1ucyB0byBqb2luKiogCgpXZSBjYW4gYWxzbyBleHBsaWNpdGx5IHRlbGwgUiB3aGljaCBjb2x1bW5zIHRvIHVzZSB0byBqb2luLiBXZSBkbyB0aGlzIGJ5IGFkZGluZyB0aGUgImJ5IiBhcmd1bWVudCBhbmQgc2V0dGluZyBpdHMgdmFsdWUgdG8gdGhlIG5hbWUgb2Ygd2hhdGV2ZXIgY29sdW1uIHdlIHdhbnQgdG8gdXNlIGluIHRoZSBqb2luLiBJbiB0aGlzIGNhc2UsIGJ5ID0gIm5jaWQiLgoKYGBge3J9CmxlZnRfam9pbih2aGlzX3lhbmNleSwgdm90ZXJzX3lhbmNleSwgYnkgPSAibmNpZCIpCmBgYAoKVGhpcyBhbHNvIHJlc3VsdHMgaW4gNzksOTE1IHJvd3MsIGJ1dCB0aGlzIHRpbWUgd2UgZ2V0IDg1IGNvbHVtbnMuIFNvIHdlIGVuZCB1cCB3aXRoIHNvbWUgZHVwbGljYXRlIGNvbHVtbnMgYmVjYXVzZSB3ZSBkaWRuJ3QgdXNlIHRoZW0gaW4gdGhlIGpvaW4uIE5vdGUgaG93IHRoZXkgYXJlIGRlc2lnbmF0ZWQgd2l0aCBhICIueSIgb3IgIi54IiB0aGF0IGdldHMgYXBwZW5kZWQgdG8gdGhlIG5hbWVzLgoKKE5vdGU6IFRoaXMgZGF0YSBpc24ndCBhbHdheXMgc28gY2xlYW4uIEkndmUgcnVuIHRoaXMgZXhhY3Qgc2FtZSBxdWVyeSB3aXRoIG9sZGVyIHZlcnNpb25zIG9mIHRoZXNlIGV4YWN0IHNhbWUgdGFibGVzIGFuZCBmb3VuZCB0aGF0IGl0IGFwcGVhcmVkIHRoYXQgdm90ZXJzIHdpdGggdGhlIHNhbWUgbmNpZCB2b3RlZCB0d2ljZSBpbiB0aGUgc2FtZSBlbGVjdGlvbi4gQWx3YXlzIGJlIG9uIHRoZSBsb29rb3V0IGZvciB0aGluZ3MgbGlrZSB3aGVuIHlvdSdyZSBqb2luaW5nIGRhdGFmcmFtZXMuIFJlbWVtYmVyOiBXaGVuIHlvdSBzZWUgdGhpbmdzIGxpa2UgdGhhdCBpdCBtZWFucyBvbmx5IG9uZSBvZiB0d28gdGhpbmdzIC0tIGEgZ29vZCBzdG9yeSBvciBiYWQgZGF0YS4gSW4gZWl0aGVyIGNhc2UsIGludGVydmlld3Mgd2l0aCBodW1hbnMgYXJlIHJlcXVpcmVkLikKCgpXaGVuIGNhbiBkcm9wIHRob3NlIHRocmVlIGR1cGxpY2F0ZSBjb2x1bW5zIGlmIHdlIHdhbnQgLi4uCmBgYHtyfQphbGxfeWFuY2V5IDwtIGxlZnRfam9pbih2b3RlcnNfeWFuY2V5LCB2aGlzX3lhbmNleSwgYnkgPSAibmNpZCIpCgphbGxfeWFuY2V5ICU+JQogIHNlbGVjdCgtY291bnR5X2lkLnksIC1jb3VudHlfZGVzYy55LCAtdm90ZXJfcmVnX251bS55KQpgYGAKCgoKKipJcyB0aGVyZSBhIHJpZ2h0X2pvaW4/KiogCgpZZXMsIHRoZXJlIGlzIHN1Y2ggYSB0aGluZyBhcyBhIHJpZ2h0IGpvaW4sIGJ1dCBJIGp1c3QgdXNlIGxlZnRfam9pbiBhbmQgY2hhbmdlIHRoZSBvcmRlciBvZiB0aGUgZGF0YWZyYW1lIGFyZ3VtZW50cy4KCgoKKipGdWxsIEpvaW5zKioKCkZ1bGwgam9pbnMgcmV0dXJuIGFsbCByb3dzIGFuZCBhbGwgY29sdW1ucyBmcm9tIGJvdGggZGF0YWZyYW1lcy4gCgpgYGB7cn0KZnVsbF95YW5jZXkgPC0gZnVsbF9qb2luKHZvdGVyc195YW5jZXksIHZoaXNfeWFuY2V5LCBieT0ibmNpZCIpCgpmdWxsX3lhbmNleQpgYGAKClRoaXMgZ2l2ZXMgdXMgODEsMjk0IHJvd3MuIFRoaXMgaXMgb25lIHJvdyBmb3IgZWFjaCByb3cgaW4gdGhlIHZvdGVyc195YW5jZXkgZGF0YWZyYW1lIHRoYXQgbWF0Y2hlcyBhIHJvdyBpbiB0aGUgdmhpc195YW5jZXkgZGF0YWZyYW1lIFBMVVMgYWxsIG9mIHRoZSByb3dzIGluIGVpdGhlciBkYXRhZnJhbWUgdGhhdCBkbyBub3QgaGF2ZSBhIG1hdGNoLgoKQXMgeW91IGNhbiBzZWUgaW4gdGhlIG5leHQgbGluZSBvZiBjb2RlLCB0aGUgb3JkZXIgb2YgYXJndW1lbnRzIGluIGZ1bGwgam9pbnMgZG9lcyBub3QgbWF0dGVyLgoKCmBgYHtyfQpmdWxsX2pvaW4odmhpc195YW5jZXksIHZvdGVyc195YW5jZXksIGJ5PSJuY2lkIikKCmBgYAoKCgoqKklubmVyIEpvaW5zKioKCldpdGggaW5uZXIgam9pbnMsIHJvd3MgdGhhdCBkb24ndCBoYXZlIGEgbWF0Y2ggYXJlIGp1c3QgZHJvcHBlZC4KCmBgYHtyfQppbm5lcl95YW5jZXkgPC0gaW5uZXJfam9pbih2aGlzX3lhbmNleSwgdm90ZXJzX3lhbmNleSwgYnk9Im5jaWQiKQoKaW5uZXJfeWFuY2V5CmBgYAoKTGlrZSBpbiBmdWxsX2pvaW5zIHRoZSBvcmRlciBvZiBhcmd1bWVudHMgZG9lcyBub3QgbWF0dGVyLgoKYGBge3J9CmlubmVyX2pvaW4odm90ZXJzX3lhbmNleSwgdmhpc195YW5jZXksICBieT0ibmNpZCIpCmBgYAoKCgoqKlByYWN0aWNhbCBBcHBsaWNhdGlvbioqCgpUaGF0J3MgYWxsIGNvb2wgaW4gdGhlb3J5LCBidXQgaW4gd2hhdCBwcmFjdGljYWwgY2FzZXMgd291bGQgeW91IHdhbnQgdG8gdXNlIG9uZSBraW5kIG9mIGpvaW4gcmF0aGVyIHRoYW4gYW5vdGhlci4KCk9uZSBleGFtcGxlIGlzIHRvIGZpbmQgdm90ZXJzIHdobyBhcmUgcmVnaXN0ZXJlZCBidXQgaGF2ZSBuZXZlciB2b3RlZC4gRm9yIHRoaXMgd2Ugd2lsbCB1c2UgYSBsZWZ0IGpvaW4uIFdlIHdpbGwgZmlyc3QgZG8gYSBsZWZ0IGpvaW4gYW5kIHRoZW4gZmlsdGVyIHRvIGZpbmQgYWxsIHJvd3MgdGhhdCBoYXZlIDxOQT4gdmFsdWVzIGluIHRoZSBlbGVjdGlvbl9sYmwgY29sdW1uLgoKUmVtZW1iZXIsIHRoaXMgaXMgc2F5aW5nOiAiSGV5IFIsIGdpdmUgbWUgYWxsIHRoZSB2b3RlcnMgZnJvbSB0aGUgdm90ZXJzX3lhbmNleSBkYXRhZnJhbWUuIE1hdGNoIHRob3NlIHRvIHRoZWlyIHZvdGluZyBoaXN0b3J5IGFueXRpbWUgdGhlcmUgaXMgYSByb3cgaW4gdGhlIHZoaXNfeWFuY2V5IGRhdGFmcmFtZSB0aGF0IGhhcyBhIG1hdGNoaW5nIG5jaWQuIgoKQmVjYXVzZSB5b3UgYXJlIGFza2luZyBmb3IgYWxsIHZvdGVycyByZWdhcmRsZXNzIG9mIHdoZXRoZXIgdGhleSBoYXZlIGEgbWF0Y2ggaW4gdGhlIHZoaXMgZGF0YWZyYW1lLCB5b3Ugd2lsbCBnZXQgPE5BPiB2YWx1ZXMgaW4gYW55IG9mIHRoZSBuY3ZoaXMgY29sdW1ucyBhbnl0aW1lIHRoZXJlIGlzbid0IGEgbWF0Y2guCgpUaGlzIG1lYW5zIHRoYXQgYWZ0ZXIgZG9pbmcgdGhlIGpvaW4sIHdlIGNhbiBmaW5kIHRoZSByb3dzIHRoYXQgaGF2ZSA8TkE+IHZhbHVlcyBpbiB0aGUgdm90ZXIgaGlzdG9yeSBkYXRhZnJhbWUgYW5kIGtub3cgdGhhdCB0aGVzZSBhcmUgdm90ZXJzIGZvciB3aG9tIHRoZXJlIGlzIG5vIHJlY29yZCBvZiB0aGVtIHBhcnRpY2lwYXRpZyBpbiBhbiBlbGVjdGlvbi4KCmBgYHtyfQphbGxfeWFuY2V5PC0gbGVmdF9qb2luKHZvdGVyc195YW5jZXksIHZoaXNfeWFuY2V5LCBieSA9ICJuY2lkIikKCmFsbF95YW5jZXkgJT4lIAogIGZpbHRlcihpcy5uYShlbGVjdGlvbl9sYmwpKQpgYGAKCkl0IGFwcGVhcnMgdGhlcmUgYXJlIDEsMzc5IHBlb3BsZSBpbiBZYW5jZXkgQ291bnR5IHdobyBhcmUgcmVnaXN0ZXJlZCBidXQgeW91IGhhdmUgbmV2ZXIgdm90ZWQuIChOb3RlOiBOb3QgYWxsIG9mIHRob3NlIHZvdGVycyBhcmUgIkFjdGl2ZSwiIGJ1dCB0aGF0J3MgYSBjb252ZXJzYXRpb24gZm9yIGEgZGlmZmVyZW50IGRheS4pCgoKSWYgd2Ugc3dpdGNoIHRoZSBvcmRlciBvZiBhcmd1bWVudHMgaW4gdGhlIGxlZnQgam9pbiwgd2Ugd291bGQgZ2V0IHRoZSBuYW1lcyBvZiBhbnlvbmUgYW55b25lIHdobyB2b3RlZCBpbiBhIFlhbmNleSBDb3VudHkgZWxlY3Rpb24gd2hvIGlzIG5vdCBjdXJyZW50bHkgcmVnaXN0ZXJlZCBpbiBZYW5jZXkgQ291bnR5LgoKYGBge3J9CmFsbF95YW5jZXk8LSBsZWZ0X2pvaW4odmhpc195YW5jZXksIHZvdGVyc195YW5jZXksICBieSA9ICJuY2lkIikKCmFsbF95YW5jZXkgJT4lCiAgZmlsdGVyKGlzLm5hKGxhc3RfbmFtZSkpCmBgYAoKWW91IHdpbGwgc2VlIHRoYXQgaXQgYXBwZWFycyBhcyBpZiB0aGVyZSBhcmUgOTIgdm90ZXIgaGlzdG9yeSByZWNvcmRzIHRoYXQgZG8gbm90IG1hdGNoIGFuIG5jaWQgb2YgYSBwZXJzb24gd2hvIGlzIG9uIHRoZSB2b3RlciByZWdpc3RyYXRpb24gcm9sZXMgaW4gWWFuY2V5IENvdW50eS4gSXQgd291bGQgYmUgaW1wb3J0YW50IHRvIHVuZGVyc3RhbmQgdGhhdCB3aHkgdGhpcyBpcyB0aGUgY2FzZS4gVGhpcyBtYXkgYmUgYSBib29ra2VlcGluZyBhbmFtb2x5IG9yIHNvbWV0aGluZyBlbHNlLgoKCldlIG1pZ2h0IHdvbmRlciB3aGV0aGVyIHRoZXNlIDkyIHZvdGVyIGhpc3RvcnkgcmVjb3JkcyB0aGF0IGRvIG5vdCBhcHBlYXIgdG8gaGF2ZSBhIG1hdGNoaW5nIHBlcnNvbiBhcmUgOTIgaW5zdGFuY2VzIG9mIGEgcGFydGljdWxhciBwZXJzb24sIDg1IGRpZmZlcmVudCBwZW9wbGUgaW4gb25lIGVsZWN0aW9uLCBvciBzb21lIGNvbWJpbmF0aW9uLiAKCkxldCdzIGFzayBSIHRvIHNob3cgdXMgdGhlIHJvd3MgdGhhdCBhcmUgbWlzc2luZyBhIGxhc3QgbmFtZS4uLiB0aGVuIGdyb3VwIHRob3NlIHJvd3MgYnkgbmNpZCAob25lIGdyb3VwIGZvciBlYWNoIHVuaXF1ZSBuY2lkKSAuLi4gdGhlbiBjb3VudCB0aGUgbnVtYmVyIG9mIHJvd3MgaW4gZWFjaCBncm91cC4uLiB0aGVuIHB1dCB0aGUgcmVzdWx0IG9mIHRoYXQgY291bnRpbmcgc3VtbWFyeSBpbnRvIGEgY29sdW1uIGNhbGxlZCAidW5saXN0ZWQiIChhbHRob3VnaCB3ZSBjb3VsZCBjYWxsIHRoYXQgY29sdW1uIGFueXRoaW5nKSAuLi4gYW5kIHRoZW4gc2hvdyB1cyB0aGUgcmVzdWx0cyBvZiBhbGwgdGhhdCBvcmRlcmVkIGJ5IHRoZSBjb3VudGluZyBzdW1tYXJ5IHZhbHVlcyBpbiB0aGUgdW5saXN0ZWQgY29sdW1uIGluIGRlc2NlbmRpbmcgb3JkZXIuCgoKYGBge3J9CmFsbF95YW5jZXkgJT4lCiAgZmlsdGVyKGlzLm5hKGxhc3RfbmFtZSkpICU+JQogIGdyb3VwX2J5KG5jaWQpICU+JQogIHN1bW1hcml6ZSh1bmxpc3RlZCA9IG4oKSkgJT4lCiAgYXJyYW5nZShkZXNjKHVubGlzdGVkKSkKYGBgCgpPSywgc28gaXQncyAzOCB1bmlxdWUgbmNpZCB2YWx1ZXMgLi4uIDM4ICJwZW9wbGUiLiBBbmQgbG9vayBhdCB0aGUgdmFsdWVzIGluIHRoZSBuY2lkIGNvbHVtbi4gVGhleSBhcmUgZGlmZmVyZW50IGxlbmd0aHMuIFRoYXQgc2VlbXMgc3RyYW5nZSBiZWNhdXNlIHVuaXF1ZSBpZGVudGlmaWVycyAtLSBsaWtlIFNvY2lhbCBTZWN1cml0eSBudW1iZXJzIC0tIGFyZSB1c3VhbGx5IHRoZSBzYW1lIG51bWJlciBvZiBjaGFyYWN0ZXJzLiAKClN1cmUgZW5vdWdoLCBpZiB3ZSBsb29rIGF0IGh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS9kbC5uY3NiZS5nb3YvZGF0YS9sYXlvdXRfbmN2b3Rlcl9uY3ZoaXMudHh0IGl0IHNheXMgdGhhdCB0aGUgbmNpZCBjb2x1bW4gc2hvdWxkIGJlIDEyIGNoYXJhY3RlcnMgbG9uZy4gKEluIHJlYWxpdHksIG1vc3QgSSBzZWUgYXJlIHNpeCBjaGFyYWN0ZXJzIGxvbmcuIEJ1dCB0aGVyZSdzIGNsZWFybHkgYSBkaXNjcmVwZW5jeSBoZXJlIHRoYXQgbWlnaHQgaGVscCB1cyBpbnF1aXJlIHdpdGggdGhlIGFnZW5jeSBhYm91dCBpdC4pCgpUaGUgbmV4dCB0aGluZyB3ZSBtaWdodCBkbyBpcyB0YWtlIGEgbG9vayBhdCBqdXN0IG9uZSBvZiB0aGVzZSBuY2lkcyBmb3Igd2hpY2ggdGhlcmUgaXMgYSBtaXNzaW5nIHBlcnNvbi4gTWF5YmUgd2Ugd2lsbCBzZWUgYSBwYXR0ZXJuLi4uIAoKYGBge3J9CmFsbF95YW5jZXkgJT4lCiAgZmlsdGVyKG5jaWQ9PSJDRzM5NTgwIikgJT4lCiAgYXJyYW5nZShlbGVjdGlvbl9sYmwpCmBgYAoKSW50ZXJlc3RpbmcuIEFsbCBvZiB0aGVzZSBzaG93IHRoYXQgdGhlICJ2b3RlZF9jb3VudHlfZGVzYyIgaXMgQ0FCQVJSVVMuIFRoYXQgbWFrZXMgbWUgd29uZGVyIGlmIHRoaXMgd2VpcmRuZXNzIGlzIGFuIGFydGlmYWN0IG9mIHBlb3BsZSBtb3ZpbmcgYmV0d2VlbiBjb3VudGllcy4gCgpJIHdvbmRlciBpZiB0aGUgdm90ZXIgaGlzdG9yeSByZWNvcmRzIHRoYXQgaGF2ZSBtaXNzaW5nIHBlb3BsZSBhbGwgY29tZSBmcm9tIGNlcnRhaW4gY291bnRpZXMgb3IgZnJvbSBjZXJ0YWluIGVsZWN0aW9uIGRhdGVzLiBNYXliZSBzb21lIG9mIHRoZSByZWNvcmRzIGp1c3QgaGF2ZW4ndCB0cmFuc2ZlcnJlZCB5ZXQuCgpMZXQncyBtYWtlIG9uZSBncm91cCBmb3IgZWFjaCBjb3VudHksIGFuZCB0aGVuIHN1Yi1ncm91cHMgZm9yIGVhY2ggZWxlY3Rpb24gZGF0ZSAoaW4gdGhpcyBjYXNlZCBjYWxsZWQgImVsZWN0aW9uX2xibCIpIGZvciBlYWNoIGNvdW50eS4KCmBgYHtyfQphbGxfeWFuY2V5ICU+JQogIGZpbHRlcihpcy5uYShsYXN0X25hbWUpKSAlPiUKICBncm91cF9ieSh2b3RlZF9jb3VudHlfZGVzYywgZWxlY3Rpb25fbGJsKSAlPiUKICBzdW1tYXJpemUodW5saXN0ZWQgPSBuKCkpICU+JQogIGFycmFuZ2UoZGVzYyh1bmxpc3RlZCkpCmBgYAoKCgoqKkZpbHRlcmluZyBKb2lucyoqCgpCZWZvcmUgd2UgZmluaXNoLCBsZXQncyBsb29rIGF0IHR3byBvdGhlciB0eXBlcyBvZiBqb2lucyB0aGF0IGFsc28gaGF2ZSBzcGVjaWZpYyBwcmFjdGljYWwgYXBwbGljYXRpb24gdG8gb3VyIHByb2JsZW0gaGVyZS4KCldlIHdpbGwgbm93IGxvb2sgYXQgc2VtaV9qb2luIGFuZCBhbnRpX2pvaW4uIFRoZXNlIGFyZSBjYWxsZWQgImZpbHRlcmluZyBqb2lucyIgYmVjYXVzZSB0aGV5IHVzZSB0aGUgc2Vjb25kIGRhdGFmcmFtZSBpbiB0aGUgZnVuY3Rpb24gdG8gZmlsdGVyIHJvd3MgZnJvbSB0aGUgZmlyc3QgZGF0YWZyYW1lLgoKCgpJZiB5b3Ugd2FudCB0byBzZWUgKm9ubHkqIHZvdGVycyB0aGF0IGhhdmUgdm90ZWQsIHVzZSAic2VtaV9qb2luIi4gQSBzZW1pX2pvaW4gaXMgbGlrZSBhbiBpbm5lcl9qb2luLCBidXQgaW5zdGVhZCBvZiBnaXZpbmcgeW91IG9uZSByb3cgZnJvbSB0aGUgZmlyc3QgZGF0YWZyYW1lIEVWRVJZIHRpbWUgaXQgbWF0Y2hlcyBhIHJvdyBpbiB0aGUgc2Vjb25kIGRhdGFmcmFtZSwgdGhpcyBnaXZlcyB5b3Ugb25seSB1bmlxdWUgdmFsdWVzIGZyb20gdGhlIGZpcnN0IGRhdGFmcmFtZS4gQW5kIGl0IG9ubHkgZ2l2ZXMgeW91IGNvbHVtbnMgZnJvbSB0aGUgZnJvbSB0aGUgZmlyc3QgZGF0YWZyYW1lLiBJdCBpcyBqdXN0ICpmaWx0ZXJpbmcqIHRoZSBmaXJzdCBkYXRhZnJhbWUsIG5vdCByZWFsbHkgam9pbmluZyBpdCB3aXRoIHRoZSBzZWNvbmQuCgpgYGB7cn0Kc2VtaV9qb2luKHZvdGVyc195YW5jZXksIHZoaXNfeWFuY2V5LCBieT0ibmNpZCIpCmBgYAoKTm90ZSB0aGF0IHRoZSBudW1iZXIgb2YgY29sdW1ucyBpbiB0aGlzIHJlc3VsdCBhcmUgZXF1YWwgdG8gdGhlIG51bWJlciBvZiBjb2x1bW5zIGluIHZvdGVyc195YW5jZXkuCgoKCklmIHlvdSB3YW50IHRvIHNlZSBvbmx5IHZvdGVycyB3aG8gaGF2ZSBORVZFUiB2b3RlZCwgd2UgdXNlIGFudGlfam9pbi4gVGhpcyBnaXZlcyB1cyBvbmx5IHJvd3MgZnJvbSB0aGUgZmlyc3QgZGF0YWZyYW1lIHRoYXQgaGF2ZSBOTyBtYXRjaGVzIGluIHRoZSBzZWNvbmQgZGF0YWZyYW1lLgoKYGBge3J9CmFudGlfam9pbih2b3RlcnNfeWFuY2V5LCB2aGlzX3lhbmNleSwgYnk9Im5jaWQiKQpgYGAKCgpTbyBub3cgd2UndmUgY292ZXJlZCB0aGUgbWFpbiB3YXlzIHlvdSB3b3VsZCBqb2luIGRhdGFmcmFtZXMgc2lkZSBieSBzaWRlLiBJbiB0aGUgbmV4dCBsZXNzb24gd2Ugd2lsbCBsb29rIGF0IGRpZmZlcmVudCB3YXlzIHRvIHB1dCBkYXRhZnJhbWVzIG9uIHRvcCBvZiBlYWNoIG90aGVyLgo=